/**
 * ECMAScript 6 新增特性中最出色的一个就是原生支持了类继承机制。虽然类继承使用的是新语法，但背后依旧使用的是原型链。
 */
;(function () {
    class Vehicle {
    }

// 继承类
    class Bus extends Vehicle {
    }

    let b = new Bus();
    console.log(b instanceof Bus); // true
    console.log(b instanceof Vehicle); // true
    function Person() {
    }

// 继承普通构造函数
    class Engineer extends Person {
    }

    let e = new Engineer();
    console.log(e instanceof Engineer); // true
    console.log(e instanceof Person); // true
    console.log(Engineer.prototype)
    console.log(Engineer.prototype.constructor === Engineer)
    console.log("==============")
})();

/**
 * 派生类都会通过原型链访问到类和原型上定义的方法。this 的值会反映调用相应方法的实例或者类
 */
;(function () {

    class Vehicle {
        identifyPrototype(id) {
            console.log(id, this)
        }

        static identifyClass(id) {
            console.log(id, this)
        }
    }

    class Bus extends Vehicle {
    }

    let v = new Vehicle()
    v.identifyPrototype('vehicle')
    Vehicle.identifyClass('vehicle')
    let b = new Bus()
    v.identifyPrototype('bus')
    Bus.identifyClass('bus')
})()

/**
 * 虽然 ECMAScript 没 有专门支持这种类的语法 ，但通过 new.target 也很容易实现。new.target 保存通过 new 关键字调用的类或函数。通过在实例化时检测 new.target 是不是抽象基类，可以阻止对抽象基类的实例化
 */
;(function () {
    // 抽象基类
    class Vehicle {
        constructor() {
            console.log(new.target);
            if (new.target === Vehicle) {
                throw new Error('Vehicle cannot be directly instantiated');
            }
        }
    }

// 派生类
    class Bus extends Vehicle {
    }

    new Bus(); // class Bus {}
    // new Vehicle(); // class Vehicle {}
// Error: Vehicle cannot be directly instantiated

})()

/**
 * 通过在抽象基类构造函数中进行检查，可以要求派生类必须定义某个方法。因为原型方法在调用类构造函数之前就已经存在了，所以可以通过 this 关键字来检查相应的方法
 */
;(function () {
    // 抽象基类
    class Vehicle {
        constructor() {
            if (new.target === Vehicle) {
                throw new Error('Vehicle cannot be directly instantiated');
            }
            if (!this.foo) {
                throw new Error('Inheriting class must define foo()');
            }
            console.log('success!');
        }
    }

// 派生类
    class Bus extends Vehicle {
        foo() {
        }
    }

// 派生类
    class Van extends Vehicle {
    }

    new Bus(); // success!
    // new Van(); // Error: Inheriting class must define foo()

    console.log("========================")
})()


/**
 * js 昆入
 * 模拟 Java 接口
 */
;(function () {
    class Vehicle {
    }

    let FooMixin = (Superclass) => class extends Superclass {
        foo() {
            console.log('foo');
        }
    };
    let BarMixin = (Superclass) => class extends Superclass {
        bar() {
            console.log('bar');
        }
    };
    let BazMixin = (Superclass) => class extends Superclass {
        baz() {
            console.log('baz');
        }
    };

    class Bus extends FooMixin(BarMixin(BazMixin(Vehicle))) {
    }

    console.log(BazMixin(Vehicle))  // [class (anonymous) extends Vehicle]

    console.log(Bus.toString())

    let b = new Bus();
    b.foo(); // foo
    b.bar(); // bar
    b.baz(); // baz
})()